from sage.structure.set_factories import SetFactory, \
        SetFactoryParent, TopMostParentPolicy
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
from sage.sets.family import Family
from sage.rings.integer import Integer
from sage.categories.examples.infinite_enumerated_sets import NonNegativeIntegers
from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets
from sage.misc.lazy_attribute import lazy_attribute
from sage.structure.list_clone import ClonableArray
from sage.structure.parent import Parent
from sage.misc.lazy_attribute import lazy_class_attribute

def to_stalactic( packed_word ):
    import collections
    pw = list(packed_word); pw.reverse()
    d = {}; li = []
    for l in pw:
        if d.has_key(l): d[l]+=1
        else: 
            d[l] = 1
            li.append(l)
    print d,li
    return Stalactic([(l,d[l]) for l in li])

class Stalactic( ClonableArray ):
    
    def __init__( self, parent, li = None, check=True ):
        if li is None:
            li = []
        print "Parent : ", parent
        print "Liste : ", li
        ClonableArray.__init__( self, parent, li, check)
        
    def check( self ):
        d = []
        try:
            for (i,j) in self:
                d.append(i)
                assert(isinstance(i, (Integer, int)) and isinstance(j, (Integer, int)))
                assert(i > 0 and j > 0), "the indice and its multiplicity must be positive integers : i:%d, m:%d"%(i,j)
        except ValueError:
            raise ValueError("elements of the list must be a tuple (i,m) where i is an indice and m its multiplicity")    
        assert(len(d) == Set(d).cardinality() 
            and min(d) == 1 
            and max(d) == len(d)), "all indices must be distincts and consecutive from 1"
        
    def __pretty_repr__(self):
        '''
        TESTS::
        
            sage: %pcp 80
            sage: from sage.combinat.some_hopf_algebra.stalactics import Stalactics
            sage: Stalactics(4).list()
            T1, 12, 21, 12, 21, 12, 21, 123, 132, 213, 231, 312, 321, 123, 132, 213, 231,
            |*  **  **  **  **  **  **  ***  ***  ***  ***  ***  ***  ***  ***  ***  *** 
            |*  *   *   **  **   *   *  *    *    *    *    *    *     *    *    *    *  
            |*  *   *            *   *                                                   
            L*                                                                           
             312, 321, 123, 132, 213, 231, 312, 321, 1234, 1243, 1324, 1342, 1423, 1432,
             ***  ***  ***  ***  ***  ***  ***  ***  ****  ****  ****  ****  ****  **** 
              *    *     *    *    *    *    *    *                                     
                                                                                        
                                                                                        
             2134, 2143, 2314, 2341, 2413, 2431, 3124, 3142, 3214, 3241, 3412, 3421, 4123,
             ****  ****  ****  ****  ****  ****  ****  ****  ****  ****  ****  ****  **** 
                                                                                          
                                                                                          
                                                                                          
             4132, 4213, 4231, 4312, 4321T
             ****  ****  ****  ****  ****|
                                         |
                                         |
                                         J 
        '''
        from sage.misc.pretty_console_print import PrettyConsoleRepr
        if len( self ) == 0:
            return PrettyConsoleRepr([""])
        return reduce(
            lambda x,y: x + y, 
            [PrettyConsoleRepr([str(c[0])] + ["*" for _ in range(c[1])]) for c in self])
            

class StalacticsFactory( SetFactory ):
    
    def __call__( self, size=None, policy=None ):
        if policy is None:
            policy = self._default_policy
        
        if isinstance( size, (Integer, int) ):
            return Stalactics_size( size, policy )
        else: return Stalactics_all( policy )
        
    @lazy_attribute
    def _default_policy( self ):
        return TopMostParentPolicy(self, (), Stalactic)
    
    def __repr__( self ):
        return "Factory for stalactics"
    
Stalactics = StalacticsFactory()
Stalactics.__doc__ = StalacticsFactory.__call__.__doc__

class Stalactics_all( SetFactoryParent, DisjointUnionEnumeratedSets ):
    def __init__( self, policy ):
        SetFactoryParent.__init__( self, (), policy,
            category = FiniteEnumeratedSets() )
        DisjointUnionEnumeratedSets.__init__(
            self, Family(NonNegativeIntegers(), Stalactics_size),
            facade = True, keepkey = False,
            category = self.category() )
        
    def __repr__( self ):
        return "All Stalactics"
    
class Stalactics_size( SetFactoryParent ):
    
    def __init__( self, size, policy):
        self._size = size
        SetFactoryParent.__init__(self, (size,), policy,
            category = FiniteEnumeratedSets())
        
    def __repr__( self ):
        return "All stalactics of size %d"%self._size
    
    def __iter__( self ):
        '''
        TESTS::
            
            sage: from sage.combinat.some_hopf_algebra.stalactics import Stalactics
            sage: Stalactics(3).list()
            [[(1, 3)], [(1, 2), (2, 1)], [(2, 2), (1, 1)], [(1, 1), (2, 2)], [(2, 1), (1, 2)], [(1, 1), (2, 1), (3, 1)], [(1, 1), (3, 1), (2, 1)], [(2, 1), (1, 1), (3, 1)], [(2, 1), (3, 1), (1, 1)], [(3, 1), (1, 1), (2, 1)], [(3, 1), (2, 1), (1, 1)]]
        '''
        from sage.combinat.permutation import Permutations
        from sage.combinat.composition import Compositions
        for i in range(1, self._size+1):
            for c in Compositions(self._size, length=i):
                for p in Permutations(i):
                    yield self._element_constructor_(
                        [( p[j], c[j] ) for j in range(len(p))] )
                    
    def cardinality( self ):
        '''
        TESTS::
        
            sage: from sage.combinat.some_hopf_algebra.stalactics import Stalactics
            sage: Stalactics(3).cardinality()
            11
        '''
        C = sage.combinat.composition.Compositions
        P = sage.combinat.permutation.Permutations
        sum = 0
        for i in range(1, self._size+1):
            sum += P( i ).cardinality() * C( self._size, length=i ).cardinality()
        return sum 
                    
    def check_element(self, el, check):
        assert(isinstance(el, Stalactic))